import traceback

import native
from trees import MagicMap
from context import Context, Reference
from object import *

def generateThreadId():
  lastid=0
  while True:
    id=lastid
    lastid=lastid+1
    yield id

threadId=generateThreadId()

class Thread:
  def __init__(self, sched, receiver, method, args, depends=None, parent=None, master=None, nested=False):
    global threadId
    self.id=threadId.next()
    self.sched=sched
    self.receiver=receiver
    self.method=method
    self.args=args
    if depends:
      self.depends=depends
    else:
      self.depends=[]
    self.parent=parent
    if master:
      self.master=master
    else:
      self.master=self

    self.nested=nested

    if len(self.depends)>0:
      self.blocked=True
    else:
      self.blocked=False

    self.terminated=False
    self.returning=False
    self.exitValue=None

    self.chained=False
    self.partial=False
    self.reuseContext=False
    self.threadMappings={}

  def terminate(self, value=None, error=False, errmsg=None):
    print 'Terminating', self.method, 'with', value, error, errmsg
    self.exitValue=value
    self.terminated=True
    self.blocked=False
    self.error=error
    self.errmsg=errmsg

  def chain(self, receiver, fmethod, fargs):
    print 'chain', fmethod, fargs
    self.chained=True
    return self.addDep(receiver, fmethod, fargs, parent=self)

  def addDep(self, receiver, method, args, parent=None, master=None, nested=False):
    self.blocked=True
    t=self.sched.addThread(receiver, method, args, parent=parent, master=master, nested=nested)
    self.depends.append(t)
    return t

  def __str__(self):
    s="[\n"

    s=s+ '  id:     '+str(self.id)+"\n"

    s=s+ '  method:     '+ str(self.method)+"\n"

    s=s+ '  args:   '+ str(self.args)+"\n"

    s=s+ "]\n"

    return s

  def invoke(self):
    print 'Invoking'
    print self

    if self.partial:
      print 'invokePartial'
      merged=self.merge()
      if merged:
        self.args=merged
        self.partial=False # Let the scheduler reschedule this
      else:
        self.terminate(error=True, errmsg='Could not merge')
        return

    self.activate()
    self.call()

  def unwind(self):
    return True

    print 'unwind'
    self.partial=True

    argNames=self.context['args']
    if argNames==None:
      argNames=[]

    if self.args.__class__==Reference:
      self.args=self.args.deref()
    self.args=self.args.clone()
    print 'unwind args:', self.args

    if len(argNames)!=len(self.args):
      print 'unwind: Type error! Not the same number of args:', len(argNames), len(self.args)
      return False

    complete=True
    for x in range(len(argNames)):
      key=argNames[x]
      arg=self.args[x]
      token=arg
      tokenType=token[0]

      if tokenType=='func':
        complete=False

        fpath=token[1][0]
        fargs=token[1][1]

        frootc=self.rootc
        fdefc=self.callc[fpath]
        print 'resolving', fpath, self.callc, fdefc
        fcallc=self.callc
        fblockc=self.blockc
        fep=fdefc['code']
        fdefc=fdefc.clone()

        t=self.addDep(frootc, fdefc, fcallc, fblockc, fargs, fep, nested=True)
        self.threadMappings[t]=x;

    return complete

  def merge(self):
    print 'merging', self.ep, self.depends
    print 'args:', self.args
    for t in self.depends:
      try:
        x=self.threadMappings[t]
        print 'replacing', self.args[x], 'with', t.exitValue
        if t.exitValue:
          self.args[x]=t.exitValue
        else:
          print 'Error! Could not merge, return value is None:', t.defc, t.ep
          return None
      except:
        print 'Could not merge', t, 'unknown thread'
        return None

    return self.args

  def call(self):
    print 'call'

    methodType=self.active.get('type').value
    if methodType=='native':
      self.callNative()
    elif methodType=='flucMethod' or methodType=='method' or methodType==None:
      self.callMethod()
    else:
      print 'Unknown type', '"'+str(methodType)+'"', str(methodType.__class__), 'cannot call'
      self.terminate(error=True, errmsg="Unknown method type %s" % (methodType))

  def activate(self):
    argNames=self.method.get('args').value
    if argNames==None:
      argNames=[]

    argValues=self.args
    if argValues.__class__==ArbreArgList:
      argValues=argValues.value

    print argNames, type(argNames), argValues, type(argValues)
    if len(argNames)!=len(argValues):
      print 'activate: Type error! Not the same number of args:', len(argNames), len(argValues)
      return None

    self.active=self.method.activate()

    for x in range(len(argNames)):
      key=argNames[x].value
      arg=argValues[x]
      print 'evalBlock:', arg, type(arg), arg.__class__
      value=self.evalBlock(arg)
      print 'Setting', key, '=', value
      self.active.put(key, value)

    print 'active:', self.active

  def callNative(self):
    code=self.active.get('code').value
    print 'code:', code, type(code)

    try:
      f=getattr(native, code)
      f(self)
      if not self.terminated and not self.chained:
        self.terminate(error=True, errmsg='Badly behaving native method, did not terminate or chain')
    except Exception, e:
      print 'Could not call native', code, e
      traceback.print_exc()
      self.terminate(error=True, errmsg="Could not call native method, error %s" % (str(e)))

  def callMethod(self):
    print 'callMethod', self.active

    block=self.active.get('code')
    receiver=self.evalReceiver(block.receiver)
    print 'receiver:', receiver
    fname=block.method
    fargs=block.args

    print 'finding method', fname, type(fname)
    fmethod=receiver.get(fname.value)
    print 'fmethod:', receiver, fname, fmethod
    if fmethod:
      t=self.chain(receiver, fmethod, fargs)
      t.unwind()
    else:
      raise Exception()

  def evalReceiver(self, r):
    print 'evalReceiver:', r
    if r.__class__==ArbreSelf:
      return self.active.method.obj
    elif r.__class__==ArbreSymbol:
      print 'symbol name:', r.name
      return self.active.get(r.name)
    else:
      return r

  def evalBlock(self, token):
    if token.__class__==ArbrePrimitiveObject:
      return token
    elif tokenType=='symdef':
      return token
    elif tokenType=='block':
      block=token.clone()
      for key in self.callc.keys():
        value=self.callc[key]
        if value.__class__==Reference:
          value=value.deref()
        block[key]=value
      return block
    elif tokenType=='symref':
      print 'symref context:', self.callc
      try:
        result=self.callc[tokenValue]
        if result.__class__==Reference:
          result=result.deref()
        print 'returning symref', tokenValue, result
      except Exception, e:
        print 'Could not find', tokenValue, 'in', self.callc, e
        traceback.print_exc()
        self.terminate(error=True, errmsg="Could not find %s" % (tokenValue))
        return None
      if not result:
        print 'ERROR! Unknown symbol', tokenValue
        return None
      return result
    elif tokenType=='func':
      print 'Error! evalBlock got a nested func', token
      return None
    else:
      print 'Unknown token type:', tokenType
      traceback.print_stack()
      return None
